4-7 作业讲解:Role关联查询Permission&拦截器序列化输出
概述
本节完成 Role 与 Permission 的关联创建、关联查询,以及通过拦截器对响应数据进行序列化输出。重点内容包括:connectOrCreate 在角色权限创建中的应用、Prisma 嵌套 include 查询、以及 class-transformer 序列化 DTO 的使用。
ValidationPipe 全局配置
要让 DTO 中的 @Type() 和 @Transform() 生效,需要在 main.ts 中启用全局转换:
// main.ts
import { ValidationPipe } from '@nestjs/common';
app.useGlobalPipes(
new ValidationPipe({
transform: true, // 启用自动类型转换
transformOptions: {
enableImplicitConversion: true, // 允许隐式类型转换(如日期字符串 → Date)
},
}),
);
typescript
这与序列化拦截器的配置类似——都需要启用隐式转换。
Role 创建时关联 Permission
DTO 设计
// role/dto/create-role.dto.ts
import { IsString, IsOptional, IsArray, ValidateNested } from 'class-validator';
import { Type } from 'class-transformer';
export class CreatePermissionDto {
@IsString()
name: string;
@IsOptional()
@IsString()
action?: string;
}
export class CreateRoleDto {
@IsString()
name: string;
@IsOptional()
@IsString()
description?: string;
@IsOptional()
@IsArray()
@ValidateNested({ each: true })
@Type(() => CreatePermissionDto) // 告诉 transformer 转换为目标类型
permissions?: CreatePermissionDto[];
}
typescript
Service 创建逻辑
// role/role.service.ts
async create(createRoleDto: CreateRoleDto) {
const { permissions, ...rest } = createRoleDto; // ES6 解构分离
return this.prismaClient.role.create({
data: {
...rest, // name, description 等字段直接传递
rolePermissions: {
create: (permissions || []).map((permission) => ({
permission: {
connectOrCreate: {
where: { name: permission.name }, // 查找已有权限
create: { // 不存在则创建
name: permission.name,
action: permission.action || '',
},
},
},
})),
},
},
});
}
typescript
connectOrCreate 工作机制
创建角色"普通用户" + 权限 ["log:read", "log:update"]
1. Permission 表中查找 name="log:read"
├── 存在 → connect(使用已有记录的 ID)
└── 不存在 → create(创建新记录并关联)
2. Permission 表中查找 name="log:update"
├── 存在 → connect
└── 不存在 → create
3. 在 RolePermission 关联表中创建 roleId ↔ permissionId 的映射
text
幂等性:多次创建相同权限的角色不会报唯一索引错误,connectOrCreate 会自动复用已有权限记录。
数据结构对应关系
创建时的嵌套结构必须与 Prisma Schema 一一对应:
Schema: 创建数据结构:
Role { ...rest }
├── rolePermissions rolePermissions: { create: [...] }
│ └── permission permission: { connectOrCreate: {...} }
│ ├── name where: { name } / create: { name, action }
│ └── action
text
Role 关联查询 Permission
嵌套 Include 查询
查询角色时,通过 include 嵌套关联 Permission:
// role/role.service.ts
async findOne(id: number) {
return this.prismaClient.role.findUnique({
where: { id },
include: {
rolePermissions: {
include: {
permission: true, // 包含完整的权限信息
},
},
},
});
}
typescript
查询结果结构
{
"id": 14,
"name": "普通用户7",
"description": null,
"rolePermissions": [
{
"roleId": 14,
"permissionId": 7,
"permission": {
"id": 7,
"name": "log:read",
"action": "read"
}
},
{
"roleId": 14,
"permissionId": 8,
"permission": {
"id": 8,
"name": "log:update",
"action": "update"
}
}
]
}
json
序列化输出 DTO
PublicRoleDto
通过 class-transformer 对响应数据进行转换,只输出需要的字段:
// role/dto/public-role.dto.ts
import { Expose, Transform, Type } from 'class-transformer';
import { CreateRoleDto } from './create-role.dto';
export class PublicRoleDto extends CreateRoleDto {
id: number;
@Expose({ name: 'rolePermissions' })
@Transform(({ value }) =>
value?.map((item: any) => item.permission?.name) || []
)
rolePermissions: string[]; // 转换为权限名称数组
}
typescript
嵌套数据取值的注意事项
Prisma include 返回的嵌套结构中,Permission 在 rolePermissions[].permission 下:
rolePermissions[].permission.name ✅ 正确
rolePermissions[].name ❌ 错误
text
// ❌ 错误:直接取 name
@Transform(({ value }) => value.map((item) => item.name))
// ✅ 正确:从嵌套的 permission 对象中取 name
@Transform(({ value }) => value.map((item) => item.permission.name))
typescript
Controller 应用序列化
// role/role.controller.ts
import { ClassSerializerInterceptor, UseInterceptors } from '@nestjs/common';
@Get(':id')
@UseInterceptors(ClassSerializerInterceptor)
findOne(@Param('id', ParseIntPipe) id: number) {
return this.roleService.findOne(id);
}
typescript
完整的创建测试流程
| 步骤 | 操作 | 数据库变化 |
|---|---|---|
| 1 | 创建角色"普通用户6"+ 权限 "log:read", "log:update" | Permission 表新增 2 条,Role 表新增 1 条,RolePermission 表新增 2 条 |
| 2 | 创建角色"普通用户7"+ 权限 "log:read", "log:update", "user:read" | Permission 表新增 1 条("user:read"),已有的 "log:read/update" 被 connect 复用 |
| 3 | 查询角色 ID=14 | 返回角色详情 + rolePermissions 列表 + permission 详情 |
Prisma 官方文档参考
| 主题 | 文档位置 |
|---|---|
| 嵌套写入 | Prisma ORM → Queries → Relation queries → Nested writes |
| connectOrCreate | Relation queries → Connect or create a record |
| connect 多条记录 | Relation queries → Connect multiple records |
| 嵌套 Include | Queries → Relation queries → Nested reads |
作业
实现一个通过 User 直接查询到其所有 Permission 的接口。思路:User → UserRole → Role → RolePermission → Permission,通过多层嵌套 include 实现一次查询获取完整权限链路。
↑